#include "PreCompile.hpp"
#include "Macro.hpp"

namespace CPreProc
{
  /////////
  // Macro
  Macro::Macro(Tokenizer &tokenizer,bool directive_read)
  {
    parse_impl(tokenizer,directive_read);
  }

  Macro::Macro(const Glib::ustring &line,bool directive_read,int hack_arg)
  {
    this->line = line;
    Tokenizer tokenizer(this->line.c_str(),this->line.c_str() + line.bytes());
    parse_impl(tokenizer,directive_read);
  }

  Macro::Macro(const Glib::ustring &symbol_name)
  {
    this->symbol_name = symbol_name;
    has_param_list = false;
    has_var_args = false;
  }

  Macro::Macro(const Glib::ustring &symbol_name,const Glib::ustring &value)
  {
    this->symbol_name = symbol_name;
    has_param_list = false;
    has_var_args = false;

    // the value has to be parsed into a replacement list
    line = value;
    Tokenizer tokenizer(line.c_str());
    parse_replacement_list(tokenizer);
  }

  Macro::Macro(const Glib::ustring &symbol_name,const Glib::ustring &value,const std::vector<Glib::ustring> &parameters)
  {
#ifndef NDEBUG
    for(int i = 0;i < (int)parameters.size();i++)
    {
      assert(validate_identifier(parameters[i]));
      // only the last parameter ay be __VA_ARGS__
      assert(parameters[parameters.size() - 1] != "__VA_ARGS__" ||
        i == (int)parameters.size() - 1);
    }
#endif

    this->symbol_name = symbol_name;
    this->has_param_list = true;
    this->parameters = parameters;
    this->has_var_args = parameters[parameters.size() - 1] == "__VA_ARGS__";

    // the value has to be parsed into a replacement list
    line = value;
    Tokenizer tokenizer(line.c_str());
    parse_replacement_list(tokenizer);
  }

  Glib::RefPtr<Macro> Macro::parse(Tokenizer &tokenizer,bool directive_read)
  {
    return Glib::RefPtr<Macro>(new Macro(tokenizer,directive_read));
  }

  Glib::RefPtr<Macro> Macro::parse(const Glib::ustring &line,bool directive_read)
  {
    return Glib::RefPtr<Macro>(new Macro(line,directive_read,0));
  }

  void Macro::parse_impl(Tokenizer &tokenizer,bool directive_read)
  {
    if(!directive_read)
    {
      // it's an error if this line isn't a macro definition,
      // but we still have to read the '#' and 'define' tokens
      // from the tokenizer, to find out where the interseting stuff
      // starts.
      if(tokenizer.read_pp_token().get_token_id() != Punct_Sharp)
        throw MacroParseException("Not a macro definition.");

      tokenizer.skip_optional_whitespace();

      if(tokenizer.read_identifier() != "define")
        throw MacroParseException("Not a macro definition.");
    }

    if(tokenizer.end_of_data())
      throw MacroParseException("Unexpected end of line. Macro name expected.");

    tokenizer.skip_required_whitespace();
    Token identifier = tokenizer.read_identifier();
    symbol_name = identifier.to_string();

    // note: the parameter list must not be preceded by whitespace.
    if(!tokenizer.end_of_data() && tokenizer.cur() == '(')
    {
      tokenizer.advance();

      // it contains a parameter list
      parse_param_list(tokenizer);

      has_param_list = true;
      if(parameters.size())
        has_var_args = parameters[parameters.size() - 1] == "__VA_ARGS__";
      else
        has_var_args = false;
    }
    else
    {
      has_param_list = false;
      has_var_args = false;
    }

    if(tokenizer.end_of_data())
    {
      // it's just a simple symbol definition, without any replacement list.
      return;
    }

    if(!has_param_list)
      tokenizer.skip_required_whitespace();
    else
      tokenizer.skip_optional_whitespace();

    // read the replacement list
    parse_replacement_list(tokenizer);
  }

  void Macro::parse_param_list(Tokenizer &tokenizer)
  {
    tokenizer.skip_optional_whitespace();
    if(tokenizer.end_of_data())
      throw MacroParseException("Unexpected end of line. ')' expected.");

    Token token = tokenizer.read_pp_token();
    if(token.get_token_id() == Punct_RightParenthesis)
      return;

    bool vararg_encountered = false;
    while(1)
    {
      if(token.get_token_id() == Punct_ThreeDots)
      {
        vararg_encountered = true;
        parameters.push_back("__VA_ARGS__");
      }
      else if(token.get_token_type() == TokenType_Identifier)
      {
        parameters.push_back(token.to_string());
      }
      else
        throw MacroParseException("Unexpected token. Identifier expected.");

      tokenizer.skip_optional_whitespace();
      if(tokenizer.end_of_data())
        throw MacroParseException("Unexpected end of line. ')' expected.");

      token = tokenizer.read_pp_token();
      if(token.get_token_id() == Punct_RightParenthesis)
      {
        return;
      }
      else if(token.get_token_id() == Punct_Comma)
      {
        if(vararg_encountered)
          throw MacroParseException("The '...' macro argument should be the last one.");

        // everything is fine, just go on with the next identifier in the param list.
      }
      else
      {
        throw MacroParseException("Unpexpected token. ',' or ')' expected.");
      }

      tokenizer.skip_optional_whitespace();
      if(tokenizer.end_of_data())
        throw MacroParseException("Unexpected end of line. Identifier expected.");
      token = tokenizer.read_pp_token();
    }
  }

  void Macro::parse_replacement_list(Tokenizer &tokenizer)
  {
    tokenizer.skip_optional_whitespace();

    while(!tokenizer.end_of_data())
    {
      Token token = tokenizer.read_pp_token();
      if(token.get_token_type() != TokenType_WhiteSpace)
        replacement_list.push_back(token);
      else
        replacement_list.push_back(Token::single_space_whitespace);
    }

    // pop any whitespace tokens from the end
    while(replacement_list.size() &&
      replacement_list[replacement_list.size() - 1].get_token_type() == TokenType_WhiteSpace)
    {
      replacement_list.pop_back();
    }
  }

  // see C standard 6.10.3 constraint 1.
  bool Macro::replacement_lists_equal(Glib::RefPtr<Macro> b)
  {
    if(replacement_list.size() != b->replacement_list.size())
      return false;

    for(int i = 0;i < (int)replacement_list.size();i++)
    {
      if(replacement_list[i].get_token_type() != b->replacement_list[i].get_token_type())
        return false;

      switch(replacement_list[i].get_token_type())
      {
      case TokenType_WhiteSpace:
        // type of whitespace doesn't matter, so the current tokens
        // are considered equal.
        break;

      default:
        if(!BuildNG::compare_string_segment(
          replacement_list[i].get_begin(),replacement_list[i].get_end(),
          b->replacement_list[i].get_begin(),b->replacement_list[i].get_end()))
        {
          return false;
        }
      }
    }

    return true;
  }

  /////////////
  // stuff related to actually applying macros to a token stream.
  class ApplyMacrosState
  {
  public:
    ApplyMacrosState(){}
    ApplyMacrosState(Tokenizer &tokenizer)
    {
      while(!tokenizer.end_of_data())
        tokens.append(tokenizer.read_pp_token());
    }

    void clear()
    {
      tokens.clear();
    }

    void to_string(BuildNG::StringBuilder<2048> &out)
    {
      for(int i = 0;i < tokens.get_size();i++)
      {
        out.append(tokens[i].get_begin(),tokens[i].get_end());
      }
    }

    BuildNG::TmpArray<Token,2048>     tokens;
    BuildNG::TmpPool<>*               mem_pool;
  };

  struct TokenRange
  {
    int begin;
    int end;
  };

  static bool apply_macros_iteration(ApplyMacrosState &in,ApplyMacrosState &out,const IMacroProvider &macro_provider);
  static void expand_object_style_macro(ApplyMacrosState &out,Glib::RefPtr<Macro> macro);
  static void expand_function_style_macro(ApplyMacrosState &in,int &read_ptr,ApplyMacrosState &out,Glib::RefPtr<Macro> macro);
  static void parse_macro_params(ApplyMacrosState &in_state,int &read_ptr,Glib::RefPtr<Macro> macro,BuildNG::TmpArray<TokenRange> &argument_values);
  static void create_string_literal(ApplyMacrosState &out,const gchar *in_begin,const gchar *in_end);
  static void apply_concat_operator(ApplyMacrosState &state,int token_range_begin,int token_range_end);

  static bool apply_macros_iteration(ApplyMacrosState &in,
    ApplyMacrosState &out,const IMacroProvider &macro_provider)
  {
    bool any_macro_applied = false;

    int read_ptr = 0;
    while(read_ptr < in.tokens.get_size())
    {
      if(in.tokens[read_ptr].get_token_type() == TokenType_Identifier)
      {
        Glib::RefPtr<Macro> macro = macro_provider.get_macro(in.tokens[read_ptr].to_string());
        if(macro)
        {
          any_macro_applied = true;

          if(macro->get_has_param_list())
          {
            read_ptr++;
            expand_function_style_macro(in,read_ptr,out,macro);
          }
          else
          {
            expand_object_style_macro(out,macro);
            read_ptr++;
          }
        }
        else
        {
          out.tokens.append(in.tokens[read_ptr]);
          read_ptr++;
        }
      }
      else
      {
        out.tokens.append(in.tokens[read_ptr]);
        read_ptr++;
      }
    }

    return any_macro_applied;
  }

  static void expand_object_style_macro(ApplyMacrosState &out,Glib::RefPtr<Macro> macro)
  {
    int expanded_begin = out.tokens.get_size();

    const std::vector<Token> &replacement_list = macro->get_replacement_list();

#ifndef NDEBUG
    if(replacement_list.size())
    {
      assert(replacement_list[0].get_token_type() != TokenType_WhiteSpace);
      assert(replacement_list[replacement_list.size() - 1].get_token_type() != TokenType_WhiteSpace);
    }
#endif

    for(int i = 0;i < (int)replacement_list.size();i++)
    {
      out.tokens.append(replacement_list[i]);
    }

    int expanded_end = out.tokens.get_size();

    apply_concat_operator(out,expanded_begin,expanded_end);
  }

  static void expand_function_style_macro(ApplyMacrosState &in,int &read_ptr,ApplyMacrosState &out,Glib::RefPtr<Macro> macro)
  {
    int expanded_begin = out.tokens.get_size();

    BuildNG::TmpArray<TokenRange> argument_values;
    parse_macro_params(in,read_ptr,macro,argument_values);

    const std::vector<Token> &replacement_list = macro->get_replacement_list();
    for(int i = 0;i < (int)replacement_list.size();i++)
    {
      const Token &cur_replacement_token = replacement_list[i];

      if(cur_replacement_token.get_token_id() == Punct_Sharp)
      {
        // skip whitespace between the # and the identifier.
        do
        {
          i++;
          if(i >= (int)replacement_list.size())
            throw SharpOperatorNeedsArgException();
        }
        while(replacement_list[i].get_token_type() == TokenType_WhiteSpace);

        if(replacement_list[i].get_token_type() != TokenType_Identifier)
          throw MacroReplaceException("Unexpected token. Identifier expected.");

        int argument_index = -1;
        for(int j = 0;j < macro->get_num_params();j++)
        {
          if(replacement_list[i] == macro->get_param_name(j))
          {
            argument_index = j;
            break;
          }
        }

        if(argument_index == -1)
          throw SharpOperatorNeedsArgException();

        BuildNG::StringBuilder<1024> string_val;
        const TokenRange &cur_arg_val = argument_values[argument_index];
        for(int i = cur_arg_val.begin;i < cur_arg_val.end;i++)
        {
          const Token &cur_arg_token = in.tokens[i];
          if(cur_arg_token.get_token_type() == TokenType_WhiteSpace)
            string_val.append(" ");
          else
            string_val.append(cur_arg_token.get_begin(),cur_arg_token.get_end());
        }

        create_string_literal(out,string_val.get_begin(),string_val.get_end());
      }
      else if(cur_replacement_token.get_token_type() == TokenType_Identifier)
      {
        int argument_index = -1;
        for(int j = 0;j < macro->get_num_params();j++)
        {
          // if the arguments list contains a ... argument,
          // the name for that argument will be __VA_ARGS__,
          // so we can just do the comparison here.
          if(cur_replacement_token == macro->get_param_name(j))
          {
            argument_index = j;
            break;
          }
        }
        
        if(argument_index != -1)
        {
          if(argument_values[argument_index].begin == argument_values[argument_index].end)
          {
            // if the argument value is empty, we have to check if it's a ## operand.
            // if it's a ## operand, we will insert a placemaker pp token,
            // otherwise, we just won't insert anything.

            bool is_concat_operand = false;
            for(int j = i - 1;j >= 0;j--)
            {
              if(replacement_list[j].get_token_id() == Punct_DoubleSharp)
              {
                is_concat_operand = true;
                break;
              }
              else if(replacement_list[j].get_token_type() != TokenType_WhiteSpace)
                break;
            }

            for(int j = i + 1;j < (int)replacement_list.size();j++)
            {
              if(replacement_list[j].get_token_id() == Punct_DoubleSharp)
              {
                is_concat_operand = true;
                break;
              }
              else if(replacement_list[i].get_token_type() != TokenType_WhiteSpace)
                break;
            }

            if(is_concat_operand)
              out.tokens.append(Token::placemaker_pp_token);
          }
          
          
          // append the tokens from the argument value
          for(int j = argument_values[argument_index].begin;j < argument_values[argument_index].end;j++)
          {
            out.tokens.append(in.tokens[j]);
          }
        }
        else
        {
          // argument_index == -1. so the identifier isn't a reference to one of the macro's arguments.
          // and thus, it's just another token we have to copy.
          out.tokens.append(cur_replacement_token);
        }
      }
      else
        out.tokens.append(cur_replacement_token);
    }

    int expanded_end = out.tokens.get_size();

    apply_concat_operator(out,expanded_begin,expanded_end);
  }

  static void parse_macro_params(ApplyMacrosState &in_state,int &read_ptr,
    Glib::RefPtr<Macro> macro,BuildNG::TmpArray<TokenRange> &argument_values)
  {
    // skip the whitespace, before the ( token
    while(read_ptr < in_state.tokens.get_size() && 
      in_state.tokens[read_ptr].get_token_type() == TokenType_WhiteSpace)
    {
      read_ptr++;
    }

    // if we are at the end of the token list, (and thus no ( was found),
    // the arg list is missing, and so we throw an exception.
    if(read_ptr == in_state.tokens.get_size())
      throw MacroArgListExpectedException(macro->get_symbol_name());

    // the current token must be (
    Token parenthesis_token = in_state.tokens[read_ptr++];
    if(parenthesis_token.get_token_id() != Punct_LeftParenthesis)
      throw MacroArgListExpectedException(macro->get_symbol_name());

    // parse the actual argument list
    bool closing_parenthesis_found = false;
    bool parsing_va_args = false;
    while(!closing_parenthesis_found)
    {
      TokenRange cur_arg_tokens;
      cur_arg_tokens.begin = read_ptr;

      // set the parsing_va_args flag, if this is a macro that has vargs,
      // and the current argument is the last one.
      parsing_va_args = macro->get_has_var_args() && 
        argument_values.get_size() == macro->get_num_params() - 1;

      int parentheses_level = 0;
      while(1)
      {
        if(read_ptr == in_state.tokens.get_size())
          throw MacroReplaceException("Unexpected end of line. ')' Expected.");

        cur_arg_tokens.end = read_ptr;
        const Token &cur_in_token = in_state.tokens[read_ptr++];

        if(parentheses_level == 0)
        {
          // if we found a comma, and are not currently parsing the __VA_ARGS__ argument,
          // we break the current argument.
          if(cur_in_token.get_token_id() == Punct_Comma && !parsing_va_args)
            break;
          else if(cur_in_token.get_token_id() == Punct_RightParenthesis)
          {
            closing_parenthesis_found = true;
            break;
          }
        }

        if(cur_in_token.get_token_id() == Punct_LeftParenthesis)
          parentheses_level++;
        else if(cur_in_token.get_token_id() == Punct_RightParenthesis)
          parentheses_level--;
      }

      assert(cur_arg_tokens.begin <= cur_arg_tokens.end);
      argument_values.append(cur_arg_tokens);

      if(argument_values.get_size() == macro->get_num_params() &&
        !closing_parenthesis_found && !macro->get_has_var_args())
      {
        throw MacroReplaceException("Too many arguments provider to macro \"" + macro->get_symbol_name() + "\".");
      }
    }

    if(argument_values.get_size() != macro->get_num_params() &&
      !macro->get_has_var_args())
    {
      throw MacroReplaceException("Not enough arguments provider to macro \"" + macro->get_symbol_name() + "\".");
    }

    // An argument value should have any leading or trailing whitespace,
    // and all whitespace inside an argument value should be collapsed into
    // a single space character.

    // fix the whitespace
    for(int i = 0;i < argument_values.get_size();i++)
    {
      // trim whitespace from the beginning of the current argument_value
      while(argument_values[i].begin < argument_values[i].end &&
        in_state.tokens[argument_values[i].begin].get_token_type() == TokenType_WhiteSpace)
      {
        argument_values[i].begin++;
      }

      if(argument_values[i].begin < argument_values[i].end)
      {
        // trim whitespace from the end of the current argument_value
        while(in_state.tokens[argument_values[i].end - 1].get_token_type() == TokenType_WhiteSpace)
        {
          argument_values[i].end--;
        }
      }

      // replace whitespace inside the argument values with just a single space
      for(int j = argument_values[i].begin;j < argument_values[i].end;j++)
      {
        if(in_state.tokens[j].get_token_type() == TokenType_WhiteSpace)
          in_state.tokens[j] = Token::single_space_whitespace;
      }
    }
  }

  static void create_string_literal(ApplyMacrosState &out,const gchar *unescaped_begin,const gchar *unescaped_end)
  {
    BuildNG::StringBuilder<1024> str_builder;
    str_builder.append("\"");
    for(const gchar *c = unescaped_begin;c != unescaped_end;c++)
    {
      if(*c == '\"')
        str_builder.append("\\\"");
      else if(*c == '\\')
        str_builder.append("\\\\");
      else
        str_builder.append(*c);
    }
    str_builder.append("\"");

    char *owned_str = (char*)out.mem_pool->alloc(str_builder.get_size());
    memcpy(owned_str,str_builder.get_begin(),str_builder.get_size());
    out.tokens.append(Token(owned_str,owned_str + str_builder.get_size(),TokenType_StringLiteral,StringLiteralType_CharString));
  }

  static void apply_concat_operator(ApplyMacrosState &state,int token_range_begin,int token_range_end)
  {
    for(int i = token_range_begin;i < token_range_end;i++)
    {
      if(state.tokens[i].get_token_id() == Punct_DoubleSharp)
      {
        if(i == token_range_begin || i == token_range_end - 1)
          throw ConcatOperatorNeedsOperandsException();

        int operand1_index = i - 1;
        while(state.tokens[operand1_index].get_token_type() == TokenType_WhiteSpace)
          operand1_index--;
        int operand2_index = i + 1;
        while(state.tokens[operand2_index].get_token_type() == TokenType_WhiteSpace)
          operand2_index++;

        const Token &operand1 = state.tokens[operand1_index];
        const Token &operand2 = state.tokens[operand2_index];

        if(operand1.get_token_type() == TokenType_PlacemakerPPToken &&
          operand2.get_token_type() == TokenType_PlacemakerPPToken)
        {
          state.tokens.replace(operand1_index,operand2_index + 1,Token::placemaker_pp_token);
        }
        else
        {
          BuildNG::StringBuilder<64> token_str;
          token_str.append(operand1.get_begin(),operand1.get_end());
          token_str.append(operand2.get_begin(),operand2.get_end());

          char *owned_token_str = (char*)state.mem_pool->alloc(token_str.get_size());
          memcpy(owned_token_str,token_str.get_begin(),token_str.get_size());

          Tokenizer tokenizer(owned_token_str,owned_token_str + token_str.get_size());
          Token token = tokenizer.read_pp_token();

          if(!tokenizer.end_of_data())
          {
            // this is undefined behaviour by the standard. Don't
            // know what to do with it yet.
            assert(!"Not implemented yet");
          }
          
          state.tokens.replace(operand1_index,operand2_index + 1,token);
        }

        // update the i and token_range variables, according to the changes
        // that have been made to state.tokens
        i = operand1_index;
        token_range_end -= operand2_index - operand1_index;
      }
    }
  }

  Glib::ustring apply_macros(const Glib::ustring &line,const IMacroProvider &macro_provider)
  {
    BuildNG::StringBuilder<2048> out;
    apply_macros(line.c_str(),line.c_str() + line.bytes(),out,macro_provider);
    return out.to_ustring();
  }

  void apply_macros(const gchar *in_begin,const gchar *in_end,
    BuildNG::StringBuilder<2048> &output,const IMacroProvider &macro_provider)
  {
    BuildNG::TmpPool<1024> mem_pool;

    Tokenizer tokenizer(in_begin,in_end);
    ApplyMacrosState state1(tokenizer);
    state1.mem_pool = &mem_pool;

    ApplyMacrosState state2;
    state2.mem_pool = &mem_pool;

    ApplyMacrosState *in_state = &state1;
    ApplyMacrosState *out_state = &state2;

    while(1)
    {
      bool any_replacement_done = apply_macros_iteration(*in_state,*out_state,macro_provider);
      if(!any_replacement_done)
        break;

      std::swap(in_state,out_state);

      out_state->clear();
    }

    out_state->to_string(output);
  }
}

